<!--
 * User: willie
 * Date: 2020/5/27
 * Time: 9:52
-->
<template>
  <div
    class="super-flow"
    :style="superflowStyle"
    ref="flow-canvas"
    @contextmenu.prevent.stop="contextmenu"
    @dblclick="LineDblclick"
  >
    <graph-line
      v-if="temEdgeConf.visible"
      :padding="linkPadding"
      :graph="graph"
      :link="temEdgeConf.link"
      :link-base-style="linkBaseStyle"
      :link-desc="linkDesc"
      :link-style="linkStyle"
    ></graph-line>

    <graph-line
      v-for="(edge, idx) in graph.linkList"
      :index="idx"
      :padding="linkPadding"
      :graph="graph"
      :link="edge"
      :key="edge.key"
      :link-base-style="linkBaseStyle"
      :link-desc="linkDesc"
      :link-style="linkStyle"
    ></graph-line>

    <mark-line
      v-if="moveNodeConf.isMove && hasMarkLine"
      :width="clientWidth"
      :height="clientHeight"
      :mark-color="markLineColor"
      :markLine="moveNodeConf.markLine"
    ></mark-line>

    <graph-node
      v-for="(node, idx) in graph.nodeList"
      :index="idx"
      :node="node"
      :graph="graph"
      :key="node.key"
      :is-move="node === moveNodeConf.node"
      :is-tem-edge="temEdgeConf.visible"
      :node-intercept="nodeIntercept(node)"
      :line-drop="linkAddable"
      :node-drop="draggable"
      @node-mousedown="nodeMousedown"
      @node-mouseenter="nodeMouseenter"
      @node-mouseleave="nodeMouseleave"
      @node-mouseup="nodeMouseup"
      @side-mousedown="sideMousedown"
      @node-contextmenu="nodeContextmenu"
      @dblclick="dblclick(node)"
      @keyup.delete="node.remove()"
    ></graph-node>

    <graph-menu
      :visible="menuConf.visible"
      :graph="graph"
      :position="menuConf.position"
      :list="menuConf.list"
      :source="menuConf.source"
    >
      <template v-slot="{ item }">
        <slot name="menuItem" :item="item"></slot>
      </template>
    </graph-menu>

    <div
      class="select-all__mask"
      ref="selectAllMask"
      tabindex="-1"
      :style="maskStyle"
      @blur="graph.graphSelected = false"
      v-show="graph.graphSelected"
      @mousedown="selectAllMaskMouseDown"
      @contextmenu.prevent.stop
    ></div>
  </div>
</template>

<script>
import Graph from "../../packages/Graph";
import GraphMenu from "../../packages/menu";
import GraphNode from "../../packages/node";
import GraphLine from "../../packages/link";
import MarkLine from "../../packages/markLine";

import { getOffset, isBool, isFun, vector, arrayReplace } from "../../packages/utils";
import mitt from "mitt";
let windowHeight = parseInt(window.innerHeight);
export default {
  emits: ["confirmEvent", "dbnodeclick"],
  name: "super-flow",
  props: {
    draggable: {
      type: Boolean,
      default: true,
    },
    linkAddable: {
      type: Boolean,
      default: true,
    },
    linkEditable: {
      type: Boolean,
      default: true,
    },
    hasMarkLine: {
      type: Boolean,
      default: true,
    },
    linkStyle: {
      type: [Function, null],
      default: null,
    },
    linkBaseStyle: {
      type: Object,
      default: () => ({}),
    },
    markLineColor: {
      type: String,
      default: "#55abfc",
    },

    linkPadding: {
      type: Number,
      default: 50,
    },
  },
  data() {
    return {
      nodeList: [],
      linkList: [],
      isView: false,
      graph: new Graph({
        width: this.width,
        height: this.height,
        origin: [681, 465],
      }),
      menuConf: {
        visible: false,
        position: [0, 0],
        source: null,
        list: [],
      },
      moveNodeConf: {
        isMove: false,
        node: null,
        offset: null,
        verticalList: [],
        horizontalList: [],
        markLine: [],
      },
      moveAllConf: {
        isMove: false,
        downPosition: [0, 0],
      },
      temEdgeConf: {
        visible: false,
        link: null,
      },
      loaded: false,
      clientWidth: 0,
      clientHeight: 0,
      emitter: mitt(),
      superflowStyle: { width: "1796px" },
      graphMenu: [
        [
          {
            label: this.$t("flowi18n.flow_property"),
            disable: false,
            icon: "el-icon-edit-outline",
            selected: (graph) => {
              this.$emit("dbflowclick", graph);
            },
          },
          {
            label: this.$t("flowi18n.flow_step_start"),
            icon: "el-icon-lx-emoji",
            disable(graph) {
              return !!graph.nodeList.find((node) => node.meta.prop === "start");
            },
            selected: (graph, coordinate) => {
              const start = graph.nodeList.find((node) => node.meta.prop === "start");
              if (!start) {
                this.buildNode(
                  "start",
                  this.$t("flowi18n.flow_step_start"),
                  50,
                  50,
                  coordinate
                );
              }
            },
          },
          {
            label: this.$t("flowi18n.flow_step_approval"),
            icon: "el-icon-lx-friend",
            disable: false,
            selected: (graph, coordinate) => {
              this.buildNode(
                "approval",
                this.$t("flowi18n.flow_step_approval"),
                160,
                80,
                coordinate
              );
            },
          },
          {
            label: this.$t("flowi18n.flow_step_circulate"),
            disable: false,
            icon: "el-icon-lx-comment",
            selected: (graph, coordinate) => {
              this.buildNode(
                "cc",
                this.$t("flowi18n.flow_step_circulate"),
                160,
                80,
                coordinate
              );
            },
          },
          {
            label: this.$t("flowi18n.flow_step_condition"),
            icon: "el-icon-close",
            selected: (graph, coordinate) => {
              this.buildNode(
                "condition",
                this.$t("flowi18n.flow_step_condition"),
                60,
                60,
                coordinate
              );
            },
          },
          {
            label: this.$t("flowi18n.flow_step_branch"),
            icon: "el-icon-video-play",
            selected: (graph, coordinate) => {
              this.buildNode(
                "branch",
                this.$t("flowi18n.flow_step_branch"),
                60,
                60,
                coordinate
              );
            },
          },
          {
            label: this.$t("flowi18n.flow_step_branch"),
            icon: "el-icon-video-pause",
            selected: (graph, coordinate) => {
              this.buildNode("endbranch", "并行-结束", 60, 60, coordinate);
            },
          },
          {
            label: this.$t("flowi18n.flow_step_end"),
            icon: "el-icon-lx-timefill",
            disable(graph) {
              return !!graph.nodeList.find((point) => point.meta.prop === "end");
            },
            selected: (graph, coordinate) => {
              this.buildNode(
                "end",
                this.$t("flowi18n.flow_step_end"),
                50,
                50,
                coordinate
              );
            },
          },
        ],
      ],
      nodeMenu: [
        [
          {
            label: this.$t("flowi18n.flow_step_property"),
            icon: "el-icon-edit-outline",
            selected: (node, coordinate) => {
              this.$emit("dbnodeclick", node, coordinate);
            },
            hidden(node) {
              return (
                node.meta.prop === "end" ||
                node.meta.prop === "branch" ||
                node.meta.prop === "condition"
              );
            },
          },
        ],
        [
          {
            label: this.$t("flowi18n.flow_node_link_remove"),
            icon: "el-icon-delete",
            disable: false,
            selected(node, coordinate) {
              console.log(coordinate);
              node.remove();
            },
          },
        ],
      ],
      linkMenu: [
        [
          {
            label: this.$t("flowi18n.flow_link_property"),
            icon: "el-icon-edit-outline",
            disable: false,
            selected: (link) => {
              this.$emit("dblineclick", link);
            },
          },
        ],
        [
          {
            label: this.$t("flowi18n.flow_node_link_remove"),
            icon: "el-icon-delete",
            disable: false,
            selected: (link) => {
              link.remove();
            },
          },
        ],
      ],
    };
  },
  components: {
    GraphMenu,
    GraphNode,
    GraphLine,
    MarkLine,
  },
  computed: {
    maskStyle() {
      const { top, right, bottom, left } = this.graph.maskBoundingClientRect;
      return {
        width: `${right - left}px`,
        height: `${bottom - top}px`,
        top: `${top + this.graph.origin[1]}px`,
        left: `${left + this.graph.origin[0]}px`,
      };
    },
  },
  mounted() {
    document.addEventListener("mouseup", this.docMouseup);
    document.addEventListener("mousemove", this.docMousemove);

    this.emitter.on("hook:beforeDestroy", () => {
      document.removeEventListener("mouseup", this.docMouseup);
      document.removeEventListener("mousemove", this.docMousemove);
    });
    this.$nextTick(() => {
      this.graph.initNode(this.nodeList);
      this.graph.initLink(this.linkList);
    });
    this.superflowStyle.height = windowHeight - 155 + "px";
    this.superflowStyle.heightInt = windowHeight - 155;
  },
  methods: {
    initMenu(menu, source) {
      return menu
        .map((subList) =>
          subList
            .map((item) => {
              let disable;
              let hidden;

              if (isFun(item.disable)) {
                disable = item.disable(source);
              } else if (isBool(item.disable)) {
                disable = item.disable;
              } else {
                disable = Boolean(item.disable);
              }

              if (isFun(item.hidden)) {
                hidden = item.hidden(source);
              } else if (isBool(item.hidden)) {
                hidden = item.hidden;
              } else {
                hidden = Boolean(item.hidden);
              }

              return {
                ...item,
                disable,
                hidden,
              };
            })
            .filter((item) => !item.hidden)
        )
        .filter((sublist) => sublist.length);
    },

    showContextMenu(position, list, source) {
      if (this.isView) return;
      if (!list.length) return;
      this.menuConf.position = position;
      this.menuConf.list = list;
      this.menuConf.source = source;
      // this.emitter.on(this.menuConf, "position", position);
      // this.emitter.on(this.menuConf, "list", list);
      // this.emitter.on(this.menuConf, "source", source);
      this.menuConf.visible = true;
    },

    docMouseup(evt) {
      if (this.isView) return;
      if (this.moveNodeConf.isMove) {
        evt.stopPropagation();
        evt.preventDefault();
      }

      this.moveNodeConf.isMove = false;
      this.moveNodeConf.node = null;
      this.moveNodeConf.offset = null;
      arrayReplace(this.moveNodeConf.markLine, []);

      this.temEdgeConf.visible = false;
      this.temEdgeConf.link = null;
      this.moveAllConf.isMove = false;
      this.menuConf.visible = false;
    },

    docMousemove(evt) {
      if (this.isView) return;
      if (this.moveNodeConf.isMove) {
        this.moveNode(evt);
      } else if (this.temEdgeConf.visible) {
        this.moveTemEdge(evt);
      } else if (this.graph.graphSelected) {
        this.moveWhole(evt);
      } else if (this.linkEditable) {
        this.graph.dispatch(
          {
            type: "mousemove",
            evt: evt,
          },
          true
        );
      }
    },

    moveNode(evt) {
      if (this.isView) return;
      const distance = 10;
      const conf = this.moveNodeConf;
      const origin = this.graph.origin;
      const position = vector(conf.offset)
        .differ(getOffset(evt, this.$el))
        .minus(origin)
        .add([conf.node.width / 2, conf.node.height / 2]).end;

      if (this.hasMarkLine) {
        const resultList = [];
        conf.verticalList.some((vertical) => {
          const x = position[0];
          const result = vertical - distance < x && vertical + distance > x;

          if (result) {
            position[0] = vertical;
            vertical = Math.floor(vertical);
            vertical += origin[0];
            vertical += vertical % 1 === 0 ? 0.5 : 0;
            resultList.push([
              [vertical, 0],
              [vertical, this.clientHeight],
            ]);
          }
          return result;
        });
        conf.horizontalList.some((horizontal) => {
          const y = position[1];
          const result = horizontal - distance < y && horizontal + distance > y;
          if (result) {
            position[1] = horizontal;
            horizontal = Math.floor(horizontal);
            horizontal += origin[1];
            horizontal += horizontal % 1 === 0 ? 0.5 : 0;
            resultList.push([
              [0, horizontal],
              [this.clientWidth, horizontal],
            ]);
          }
          return result;
        });

        arrayReplace(conf.markLine, resultList);
      }
      ////添加拖拽时的零界点
      if (
        position != null &&
        conf != null &&
        conf.node != null &&
        conf.node.center != null
      ) {
        if (conf.node.meta.prop == "start" || conf.node.meta.prop == "end") {
          if (position[0] < -640) {
            position[0] = -640;
          }
          let width = document.getElementsByClassName("super-flow")[0].clientWidth;
          if (position[0] > width / 1.68) {
            position[0] = width / 1.68;
          }
          if (position[1] < -430) {
            position[1] = -430;
          }
          let height = this.superflowStyle.heightInt;
          if (position[1] > height / 2.43) {
            this.superflowStyle.height = height + 10 + "px";
            this.superflowStyle.heightInt = height + 10;
          }
        } else {
          if (position[0] < -585) {
            position[0] = -585;
          }
          let width = document.getElementsByClassName("super-flow")[0].clientWidth;
          if (position[0] > width / 1.77) {
            position[0] = width / 1.77;
          }
          let height = this.superflowStyle.heightInt;
          if (position[1] > height / 2.5) {
            this.superflowStyle.height = height + 10 + "px";
            this.superflowStyle.heightInt = height + 10;
          }
          if (position[1] < -410) {
            position[1] = -410;
          }
        }
        conf.node.center = position;
      }
    },

    moveTemEdge(evt) {
      if (this.isView) return;
      this.temEdgeConf.link.movePosition = getOffset(evt, this.$el);
    },

    moveWhole(evt) {
      if (this.isView) return;
      if (this.moveAllConf.isMove) {
        const offset = vector(this.moveAllConf.downPosition).differ([
          evt.clientX,
          evt.clientY,
        ]).end;
        arrayReplace(this.graph.origin, vector(this.moveAllConf.origin).add(offset).end);
      }
    },

    ///右键菜单
    contextmenu(evt) {
      if (this.isView) return;
      const mouseonLink = this.graph.mouseonLink;
      const position = getOffset(evt);
      let list, source;
      if (position[1] > this.superflowStyle.heightInt - 80) {
        this.superflowStyle.heightInt = this.superflowStyle.heightInt + 80;
        this.superflowStyle.height = this.superflowStyle.heightInt + 80 + "px";
      }
      if (mouseonLink && mouseonLink.isPointInLink(position)) {
        list = this.initMenu(this.linkMenu, mouseonLink);
        source = mouseonLink;
      } else {
        if (mouseonLink) this.graph.mouseonLink = null;
        list = this.initMenu(this.graphMenu, this.graph);
        source = this.graph;
      }

      this.showContextMenu(position, list, source);
    },
    ///连线双击
    LineDblclick(evt) {
      if (this.isView) return;
      const mouseonLink = this.graph.mouseonLink;
      const position = getOffset(evt);
      if (mouseonLink && mouseonLink.isPointInLink(position)) {
        this.$emit("dblineclick", mouseonLink);
      }
    },
    ///拖拽后事件
    nodeMousedown(node, offset) {
      if (this.isView) return;
      if (this.draggable) {
        this.clientWidth = this.$el.clientWidth;
        this.clientHeight = this.$el.clientHeight;

        const verticalList = this.moveNodeConf.verticalList;
        const horizontalList = this.moveNodeConf.horizontalList;

        const centerList = this.graph.nodeList
          .filter((item) => item !== node)
          .map((node) => node.center);

        arrayReplace(
          verticalList,
          [...new Set(centerList.map((center) => center[0]))].sort(
            (prev, next) => prev - next
          )
        );

        arrayReplace(
          horizontalList,
          [...new Set(centerList.map((center) => center[1]))].sort(
            (prev, next) => prev - next
          )
        );

        this.moveNodeConf.isMove = true;
        this.moveNodeConf.node = node;
        this.moveNodeConf.offset = offset;
      }
    },

    nodeMouseenter(evt, node, offset) {
      if (this.isView) return;
      const link = this.temEdgeConf.link;
      if (this.enterIntercept(link.start, node)) {
        link.end = node;
        link.endAt = offset;
      }
    },
    duplicate(totalList) {
      var temp = []; //一个新的临时数组
      for (var i = 0; i < totalList.length; i++) {
        if (temp.indexOf(totalList[i]) == -1) {
          temp.push(totalList[i]);
        }
      }
      return temp;
    },
    nodeMouseleave() {
      if (this.isView) return;
      this.temEdgeConf.link.end = null;
    },

    nodeMouseup() {
      if (this.isView) return;
      this.graph.addLink(this.temEdgeConf.link);
    },

    nodeContextmenu(evt, node) {
      if (this.isView) return;
      const list = this.initMenu(this.nodeMenu, node);
      if (!list.length) return;
      this.menuConf.position = getOffset(evt, this.$el);
      this.menuConf.list = list;
      this.menuConf.source = node;
      this.menuConf.visible = true;
    },

    sideMousedown(evt, node, startAt) {
      if (this.isView) return;
      if (this.linkAddable) {
        const link = this.graph.createLink({
          start: node,
          startAt,
        });
        link.movePosition = getOffset(evt, this.$el);
        this.temEdgeConf.link = link;
        this.temEdgeConf.visible = true;
      }
    },

    nodeIntercept(node) {
      return () => this.outputIntercept(node);
    },

    menuItemSelect() {
      this.menuConf.visible = false;
    },

    selectAllMaskMouseDown(evt) {
      this.moveAllConf.isMove = true;
      this.moveAllConf.origin = [...this.graph.origin];
      this.moveAllConf.downPosition = [evt.clientX, evt.clientY];
    },

    selectedAll() {
      this.graph.selectAll();
    },

    toJSON() {
      return this.graph.toJSON();
    },

    getMouseCoordinate(clientX, clientY) {
      const offset = getOffset({ clientX, clientY }, this.$el);
      return vector(offset).minus(this.graph.origin).end;
    },

    addNode(options) {
      if (this.isView) return;
      return this.graph.addNode(options);
    },
    dblclick(item) {
      if (this.isView) return;
      this.$emit("dbnodeclick", item, []);
    },
    enterIntercept(formNode, toNode) {
      const formType = formNode.meta.prop;
      switch (toNode.meta.prop) {
        case "start":
          return false;
        case "approval":
          return ["condition", "branch", "endbranch", "approval"].includes(formType);
        case "condition":
          return ["start", "approval"].includes(formType);
        case "end":
          return ["approval", "endbranch"].includes(formType);
        case "branch":
          return ["condition", "start", "approval"].includes(formType);
        case "cc":
          return ["approval", "cc"].includes(formType);
        default:
          return true;
      }
    },
    outputIntercept(node) {
      return !(node.meta.prop === "end");
    },
    linkDesc(link) {
      return link.meta !== null
        ? this.$i18n.locale === "zh-cn"
          ? link.meta.cnName
          : link.meta.enName
        : "节点连线";
    },
    ///按钮创建节点
    installNode(prop, name, width, height) {
      let coordinate = [-605, -405];
      if (this.graph.nodeList.length == 0) {
        coordinate = [-605, -405];
      } else {
        this.graph.nodeList.forEach((p) => {
          coordinate[0] = p.coordinate[0];
          coordinate[1] = p.coordinate[1];
        });
        coordinate[0] = coordinate[0] + 170;
        if (coordinate[0] > 935) {
          coordinate[1] = coordinate[1] + 90;
          coordinate[0] = -605;
        }
        if (coordinate[1] > 295) {
          this.superflowStyle.height = this.superflowStyle.heightInt + 50 + "px";
          this.superflowStyle.heightInt = this.superflowStyle.heightInt + 50;
        }
      }
      var start = false;
      if (prop === "start") {
        start = this.graph.nodeList.find((node) => node.meta.prop === "start");
      }
      if (prop === "end")
        start = this.graph.nodeList.find((node) => node.meta.prop === "end");
      if (!start) this.buildNode(prop, name, width, height, coordinate);
    },
    ///拖拽创建节点
    installDragNode(prop, name, width, height, coordinate) {
      var start = false;
      if (prop === "start") {
        start = this.graph.nodeList.find((node) => node.meta.prop === "start");
      }
      if (prop === "end")
        start = this.graph.nodeList.find((node) => node.meta.prop === "end");
      if (!start) this.buildNode(prop, name, width, height, coordinate);
    },
    ///创建节点
    buildNode(prop, name, width, height, coordinate) {
      this.graph.addNode({
        id: "",
        key: "",
        width: width == undefined ? 160 : width,
        height: height == undefined ? 80 : height,
        coordinate: coordinate,
        meta: {
          stepId: "", ///节点ID
          versionId: "", ///流程版本ID
          prop: prop, ///节点类型
          width: width == undefined ? 160 : width, ///宽度
          height: height == undefined ? 80 : height, ///导读
          coordinate: JSON.stringify(coordinate), ///坐标
          desc: "", ///说明
          cnName: name, ///中文名
          enName: name, ///英文名
          showQuickField: true, ///快捷字段显示
          showHistory: true, ///显示历史
          opinion: "", ///意见
          backOpinion: "", ///退回意见
          endOpinion: "", ///终止意见
          showOpinion: true, ///显示意见
          required: false, ///意见必填
          handlerMessage: true, ///处理人接收提醒
          ccMessage: true, ///抄送人接收提醒
          note: "", ///备注
          groups: "",
          startGroup: "",
          backScale: 0,
          approvalScale: 0,
          backStrategy: 0,
          branchStrategy: 0,
          endScale: 0,
          endStrategy: 0,
        },
        buttons: [],
        form: null,
        strategy: {
          stepId: "", ///节点id
          isAutoSelectStep: false,
          approvalStrategy: 0, //会签策略
          skipStrategy: 0, ///跳过策略
          strategyType: 0, ///策略类型
          approveParttimePostion: 0, ///审批人兼职策略
          sendParttimePostion: 0, ///发送人兼职策略
          approverType: 0, ///处理者类型
          formField: "", ///表单字段
          formFieldApproveType: 0, ///表单字段处理者类型
          appoverRange: "", ///表单字段处理者类型
          defaultApprover: "", ///默认处理人
          orgLeve: 0, ///部门负责人层级
          ccStrategyType: 0, ///抄送策略类型
          ccApproveParttimePostion: 0, ///抄送人兼职设置
          ccSendParttimePostion: 0, ///抄送人兼职2
          ccType: 0, ///抄送人类型
          ccFormFieldApproveType: 0, ////抄送表单字段处理者类型
          ccFormField: "", ///抄送表单字段
          ccUser: "", ////默认抄送人
          nodeApprovalScale: 0, ///审批比例
          nodeBackScale: 0, ///审批比例
          nodeBackStrategy: 0, ///退回策略
          nodeEndScale: 0, ///审批比例
          nodeEndStrategy: 0, ///终止回策略
        },
        complateds: [],
      });
    },
  },
  watch: {
    "graph.graphSelected"() {
      if (this.graph.graphSelected) {
        this.$nextTick(() => {
          this.$refs.selectAllMask.focus();
        });
      }
    },
    "graph.mouseonLink"() {
      if (this.graph.mouseonLink) {
        document.body.style.cursor = "pointer";
      } else {
        document.body.style.cursor = "";
      }
    },
    origin() {
      this.graph.origin = this.origin || [];
    },
    nodeList() {
      this.graph.initNode(this.nodeList);
    },
    linkList() {
      this.graph.initLink(this.linkList);
    },
  },
  install(Vue) {
    Vue.component(this.name, this);
  },
};
</script>
